﻿using System;
using System.Net;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using PI_CS_Tools;
using System.Linq;
using TransportLayer;
using  PI_CS_Library;
using  PI_CS_Library.Virtual_Network_Information_Model;
using PI_CS_Library.PicsNetwork_Information_Model;

namespace  PI_CS_Library
{
    public class PicsServer
    {
        private PicsNetworkController _nc;

        public class VNOEntry
        {
            public int vnoID;
            public string Name;
            public Socket SlaveSocket;
            public Socket MasterSocket;
            public byte[] SlaveBuffer = new byte[4086];
            public byte[] MasterBuffer = new byte[4086];
            public bool Connected { get { return SlaveSocketConnected && MasterSocketConnected; } }
            public bool SlaveSocketConnected;
            public bool MasterSocketConnected;
            //public List<int> ConnectedVirtualNetworks= new List<int>();
            //public bool Status;
            public int counter = 0;
        }
        /*
        private class VirtualNetworkDirectory
        {
            public string filePath;
            public int VirtualNetworkID;
            public int ccport;
        }
         */
        //for virtual netowrk operators
        private Dictionary<int, VNOEntry> VnoDirectory = new Dictionary<int, VNOEntry>();
        //for virtual network
        private Dictionary<int, int> VnDirectory = new Dictionary<int, int>();
        private Dictionary<string, int> LoginDirectory = new Dictionary<string, int>();
        private Socket socket;
        private GUIDelegate _gui;
        private int port = 1234;
        public int Port
        {
            get
            {
                return port;
            }
        }

        private void Init()
        {
            byte[] _buffer = new Byte[1024];
            this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPEndPoint ip = new IPEndPoint(IPAddress.Any, port); //any means at all network interfaces
            this.socket.Bind(ip);
            this.socket.Listen(10);
            this.socket.BeginAccept(OnClientConnect, this.socket);

        }

        public PicsServer(PicsNetworkController _nc, GUIDelegate _gui)
        {
            this._nc = _nc;
            LoginDirectory = Serial.DeserializeDictionary("accountsList.xml");
            this.Init();
            this._gui = _gui;
        }


        private void OnClientConnect(IAsyncResult asyn)
        {
            VNOEntry client = new VNOEntry();

            Socket onSocket = this.socket.EndAccept(asyn);
            //client.MasterSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            byte[] buffor = new byte[100];
            int received = onSocket.Receive(buffor);
            string[] login = (Encoding.ASCII.GetString(buffor, 0, received)).Split(' ');
            if (login.Length == 2 && login[0] == "Register" && LoginDirectory.ContainsKey(login[1]))
            {
                client.Name = login[1];
                client.vnoID = LoginDirectory[login[1]];

                if (!VnoDirectory.ContainsKey(client.vnoID))
                {
                    VirtualNetworkOperatorInfo _vInfo;
                    VnoDirectory.Add(client.vnoID, client);
                    if (!_nc.VNOperators.ContainsKey(client.vnoID))
                    {
                        _vInfo = new VirtualNetworkOperatorInfo(client.Name, client.vnoID);
                        _nc.AddVNO(_vInfo);
                    }
                    //_gui(this, new GUIArgs("newVNO#"+ client.vnoID));
                    onSocket.Send(Encoding.ASCII.GetBytes("Register OK " + _nc.ManagerEngine.NetworkName));
                    client.SlaveSocket = onSocket;
                    client.SlaveSocket.BeginReceive(client.SlaveBuffer, 0, client.SlaveBuffer.Length, SocketFlags.None, OnDataReceived, client);
                    //client.RemoteSocket.Send(Encoding.ASCII.GetBytes("register"));
                   // int r = client.RemoteSocket.Receive(client.Buffor2);
                   // string response = Encoding.ASCII.GetString(client.Buffor2, 0, r);
                    client.SlaveSocketConnected = true;
                    _gui(this, new GUIArgs("newVNO#" + client.vnoID));

                }
                else if (!VnoDirectory[client.vnoID].Connected)
                {
                    VnoDirectory[client.vnoID].MasterSocket = onSocket;
                    VnoDirectory[client.vnoID].MasterSocketConnected = true;
                    VnoDirectory[client.vnoID].MasterSocket.Send(Encoding.ASCII.GetBytes("Register OK " + _nc.ManagerEngine.NetworkName));
                }
                else
                {
                    client.SlaveSocket.Send(Encoding.ASCII.GetBytes("rejected"));
                    socket.Close();
                }

            }
            else
            {
                client.SlaveSocket.Send(Encoding.ASCII.GetBytes("rejected"));
                socket.Close();
            }
            //this.socket.Listen(10);
            this.socket.BeginAccept(OnClientConnect, this.socket);
        }

        private void OnDataReceived(IAsyncResult asyn)
        {
            VNOEntry client = (VNOEntry)asyn.AsyncState;
            int r = 0;
            try { r = client.SlaveSocket.EndReceive(asyn); }
            catch (SocketException)
            {
                client.SlaveSocket.Close();
                VnoDirectory.Remove(client.vnoID);
                return;
            }
            if (r == 0)
            {
                client.SlaveSocket.Close();
                VnoDirectory.Remove(client.vnoID);
                return;
            }
            string recv = Encoding.ASCII.GetString(client.SlaveBuffer, 0, r);
            string response = ProcessQuery(recv, client); 
            client.SlaveSocket.Send(Encoding.ASCII.GetBytes(response));
            client.SlaveSocket.BeginReceive(client.SlaveBuffer, 0, client.SlaveBuffer.Length, SocketFlags.None, OnDataReceived, client);
        }


        

        public void Send(VNOEntry client, string message)
        {
            try
            {
                client.MasterSocket.Send(Encoding.ASCII.GetBytes(message));
            }
            catch (SocketException) { }
            catch (NullReferenceException) { }
        }

        public void BroadcastMessage(string message)
        {
            foreach (VNOEntry _vno in VnoDirectory.Values)
            {
                Send(_vno, message);
            }
        }

        private string ProcessQuery(string recv, VNOEntry client)
        {
            string response = "";
            int vnID;
            int matrixToConnectID;
            int sinkId;
            int sourceId;
            int targetId;
            int ccport;
            int capacity;
            int virtualnetworkConnectionNumber;
            int _userLinkId;

            Identification _vnoId = _nc.GetVNO(client.vnoID).Id;

            string[] param = recv.Split(' ');

            try
            {
                switch (param[0])
                {
                    case "GetAccessibleMatrixes":
                        response = "GetAccessibleMatrixes OK " + _nc.Network.GetMatrixList();
                        break;

                    case "AddVirtualNetwork":
                        string factors = param[1];
                        int _VNid = _nc.SetUpVirtualNetwork(factors, _vnoId);
                        _gui(this, new GUIArgs("newVirtualNetwork#" + _vnoId + "#" + _VNid));
                        response = "AddVirtualNetwork OK " + _VNid;
                        break;

                    case "AddVirtualLink":
                        int _vnID = Int32.Parse(param[1]);
                        Virtual_Network_Information_Model.Link _newLink;
                        try
                        {
                            _newLink = _nc.AddVirtualLink(_vnID, param[2]);
                        }
                        catch (ResourceException)
                        {
                            response = "AddVirtualLink Fail";
                            string[] values = param[2].Split(':');
                            _gui(this, new GUIArgs("newVirtualLinkFailed#" + _vnoId + "#" + _vnID + "#" + _nc.Network.GetMatrix(Int32.Parse(values[0])).Name
                             + "#" + _nc.Network.GetMatrix(Int32.Parse(values[1])).Name));
                            break;
                        }
                        _gui(this, new GUIArgs("newVirtualLink#" + _vnoId + "#" + _vnID + "#" + _newLink.SrcMatrix.Name 
                             + "#" + _newLink.SinkMatrix.Name));

                        response = "AddVirtualLink OK " + _newLink.SrcMatrix.Id.Number + " " + _newLink.SinkMatrix.Id.Number + " " + _newLink.Id.Number;
                        break;

                    case "AddSinkTTP":
                        //todo: Message:add_sink {vnID} {matrixToConectID} {callcontrollerPort}  
                        //Response: ok/reject

                        vnID = Int32.Parse(param[1]);
                        matrixToConnectID = Int32.Parse(param[2]);
                        //ccport = Int32.Parse(param[3]);

                        TransportLayer.Node sink = new TransportLayer.Node();
                        sink.Id = _nc.ManagerEngine.GetNodeFreeID();
                        sink.Type = "Sink";
                        sink.Name = "Sink_" + sink.Id;

                        _userLinkId = _nc.ManagerEngine.AddSink(sink, matrixToConnectID);
                        Thread.Sleep(100);
                        if (_userLinkId != -1)
                        {
                            int _portId = _nc.ManagerEngine.GetCallerPort(sink.Id);
                            int _dstPort = _nc.ManagerEngine.Links[_userLinkId].TargetPort;
                            int virtualMatrixId = _nc.Network.GetHostedMatrixId(matrixToConnectID, vnID);
                            _nc.Network.GetVirtualNetwork(vnID).ConnectSink(sink.Id, _userLinkId, _dstPort, virtualMatrixId);
                            response += String.Format("AddSinkTTP OK " + sink.Id + " " + _portId);
                        }
                        else response += "AddSinkTTP Fail";
                        break;

                    case "AddSourceTTP":
                        //todo: Message:add_source {vnID} {matrixToConectID} {callcontrollerPort}  
                        //Response: ok/reject

                        vnID = Int32.Parse(param[1]);
                        matrixToConnectID = Int32.Parse(param[2]);
                        //ccport = Int32.Parse(param[3]);

                        TransportLayer.Node source = new TransportLayer.Node();
                        source.Id = _nc.ManagerEngine.GetNodeFreeID();
                        source.Type = "Source";
                        source.Name = "Source_" + source.Id;
                        _userLinkId = _nc.ManagerEngine.AddSource(source, matrixToConnectID);
                        Thread.Sleep(100);
                        if (_userLinkId != -1)
                        {
                            int _portId = _nc.ManagerEngine.GetCallerPort(source.Id);
                            int _sourcePort = _nc.ManagerEngine.Links[_userLinkId].SourcePort;
                            int virtualMatrixId = _nc.Network.GetHostedMatrixId(matrixToConnectID, vnID);
                            _nc.Network.GetVirtualNetwork(vnID).ConnectSource(source.Id, _userLinkId, _sourcePort, virtualMatrixId);

                            response += String.Format("AddSourceTTP OK " + source.Id + " " + _portId);
                        }
                        else response += "AddSourceTTP Fail";
                        break;

                    case "NewNetworkConnection":
                        //todo: Message: connect {vnID} {5:4:[..]:6:8}  
                        //todo: Response: ok/reject

                        vnID = Int32.Parse(param[1]);
                        sourceId = Int32.Parse(param[2]);
                        sinkId = Int32.Parse(param[3]);
                        capacity = Int32.Parse(param[5]);
                        
                        //param[4] is a path (example: 2:4:5) where numbers are virtual nodes id's 
                        List<Virtual_Network_Information_Model.Matrix> _matrixList = new List<Virtual_Network_Information_Model.Matrix>();
                        //List<Virtual_Network_Information_Model.Link> _linkList = new List<Virtual_Network_Information_Model.Link>();
                        Dictionary<int, Virtual_Network_Information_Model.Link> _virtualLinkList;
                        //TO check:
                         string[] _linkStrId = param[4].Split(':');

                         for (int i = 0; i < _linkStrId.Length; i++)
                             if (i % 2 == 0)
                                 _matrixList.Add(_nc.Network.GetVirtualNetwork(vnID).GetMatrix(Int32.Parse(_linkStrId[i])));
          
                        _virtualLinkList = _nc.Network.GetVirtualNetwork(vnID).GetLinkListFromMatrixList(_matrixList, capacity);

                        NwTTP _source = _nc.Network.GetVirtualNetwork(vnID).GetNwTTP(sourceId);
                        NwTTP _sink = _nc.Network.GetVirtualNetwork(vnID).GetNwTTP(sinkId);

                        Dictionary<int, Dictionary<int, PicsLink>> _allLinkList = _nc.MapVirtualLinks(_virtualLinkList);
                        Virtual_Network_Information_Model.NetworkConnection _newConnection = _nc.SNCC.SetUpNetworkConnection(-1, vnID, _allLinkList, _source, _sink, capacity);

                        string _output=null;

                        for (int k=0; k < _newConnection.LinkConnectionsList.Count; k++)
                        {
                            _output += _newConnection.MatrixConnectionsList.ElementAt(k).Key + ":" + _newConnection.LinkConnectionsList.ElementAt(k).Key + ":";
                        }
                        _output += _newConnection.MatrixConnectionsList.Last().Key;

                        response += String.Format("NewNetworkConnection OK " + _newConnection.Id.Number + " " + _output);

                        break;

                    case "TeardownNetworkConnection":
                        //todo: Message: teardown {vnID} {virtualnetworkConnectionNumber}
                        //Response: ok/reject
                        vnID = Int32.Parse(param[1]);
                        virtualnetworkConnectionNumber = Int32.Parse(param[2]);

                        _nc.SNCC.TearDownNetworkConnection(vnID, virtualnetworkConnectionNumber);
                        response += String.Format("TeardownNetworkConnection OK");

 
                        break;

                    case "RemoveVirtualLink":
                        string _src = _nc.Network.GetVirtualNetwork(Int32.Parse(param[1])).GetLink(Int32.Parse(param[2])).SrcMatrix.Name;
                        string _dst = _nc.Network.GetVirtualNetwork(Int32.Parse(param[1])).GetLink(Int32.Parse(param[2])).SinkMatrix.Name;
                        _nc.RemoveVirtualLink(Int32.Parse(param[1]), Int32.Parse(param[2]), true);
                        _gui(this, new GUIArgs("removeVirtualLink#" + _nc.GetVNO(_vnoId.Number).Login + "#" + param[1] + "#" + param[2]
                            + "#" + _src + "#" + _dst));
                        response += String.Format("RemoveVirtualLink OK");
                        break;

                    case "RemoveVirtualNetwork":
                        _nc.RemoveVirtualNetwork(Int32.Parse(param[1]));
                        _gui(this, new GUIArgs("removeVirtualNetwork#" + _nc.GetVNO(_vnoId.Number).Login + "#" + param[1]));
                        response += String.Format("RemoveVirtualNetwork OK");
                        break;

                    case "Logout":
                        response += String.Format("Logout OK");
                        VnoDirectory.Remove(client.vnoID);
                        _gui(this, new GUIArgs("logout#" + _nc.GetVNO(_vnoId.Number).Login));
                        break;

                    default:
                        response += "reject";
                        break;


                }

            }
            catch (FormatException) { response = "reject format_error"; }
            catch (NullReferenceException) { response = "reject not_all_blank_fill"; }

            return response;
        }

        public Dictionary<int, VNOEntry> VNODirectory
        {
            get { return VnoDirectory; }
            set { VnoDirectory = value; }
        }
    }
}